<?php
	// Awesome bytewise finite state machine is awesome
	abstract class FSM {
		protected $state, $accepted;
		
		function __construct() {
			$this -> state = 0;
			$this -> accepted = array();
		}
		
		public function read($data) {
			for ($i = 0; $i < strlen($data); $i++) {
				$this -> transition($data[$i]);
			}
			
			return $this -> accept();
		}
		
		protected abstract function accept();
		protected abstract function add();
		protected abstract function reset();
		protected abstract function transition($char);
	}
	
	class Ftse_FSM extends FSM {
		private $Symbol, $Name, $Timestamp, $Bid, $Ask, $Volume;

		
		function __construct() {
			parent::__construct();
		}
		
		protected function accept() {
			$return = $this -> accepted;
			$this -> accepted = array();
			
			return $return;
		}
		
		protected function add() {			
			if (in_array('0', array($this -> Bid, $this -> Ask, $this -> Volume))) {
				// Corrupt data, ignore
				$this -> reset();
				return;
			}
			
			$this -> accepted[] = array(
				'Symbol' => str_replace('"', '', $this -> Symbol),
				'Name' => str_replace('"', '', $this -> Name),
				'Timestamp' => strtotime($this -> Timestamp),
				'Bid' => $this -> Bid,
				'Ask' => $this -> Ask,
				'Volume' => $this -> Volume,
				'Spread' => round($this -> Ask - $this -> Bid, 2)
			);
			
			$this -> reset();
		}
		
		protected function reset() {
			$this -> state = 0;
			$this -> Symbol = '';
			$this -> Name = '';
			$this -> Timestamp = '';
			$this -> Bid = '';
			$this -> Ask = '';
			$this -> Volume = '';
		}
		
		protected function transition($char) {
			$alter = NULL;
			
			switch ($this -> state) {
				case 0: $alter = &$this -> Symbol; break;
				case 1: $alter = &$this -> Name; break;
				case 2: $alter = &$this -> Timestamp; break;
				case 3: $alter = &$this -> Bid; break;
				case 4: $alter = &$this -> Ask; break;
				case 5: $alter = &$this -> Volume; break;
			}
			
			if ($this -> state === 5 && $char === '"') {
				// GIVE ME AN END OF CHUNK MARKER, YOU BASTARDS
				$this -> add();
				$alter = &$this -> Symbol;
			}
			
			if ($char === ',') $this -> state++;
			elseif ($char === "\n") $this -> add();
			else $alter .= $char;
		}
	}
	
	class News_FSM extends FSM {
		private $Source, $Timestamp, $Title, $Body;
		private $Buffer;
		
		function __construct() {
			parent::__construct();
		}
		
		protected function accept() {
			$return = $this -> accepted;
			$this -> accepted = array();
			
			return $return;
		}
		
		protected function add() {
			$this -> generateTimestamp();
			
			$this -> accepted[] = array(
				'Source' => $this -> Source,
				'Timestamp' => $this -> Timestamp,
				'Title' => $this -> Title,
				'Body' => $this -> Body
			);
			
			$this -> reset();
		}
		
		private function generateTimestamp() {
			$val = $this -> Timestamp;
			$x = strpos($val, '/');
			$val = substr($val, 0, $x - 2) . substr($val, $x + 1, 3) . substr($val, $x - 2, 2) . substr($val, $x + 3);
			$this -> Timestamp = strtotime($val);
		}
		
		protected function reset() {
			$this -> state = 0;
			$this -> Source = '';
			$this -> Timestamp = '';
			$this -> Title = '';
			$this -> Body = '';
			$this -> Buffer = '';
		}
		
		protected function transition($char) {
			if ($char === "\n") goto end;
			$alter = NULL;
			$new_state = -1;
			
			switch ($this -> state) {
				case 0: $alter = &$this -> Buffer; break;
				case 1: $alter = &$this -> Source; break;
				case 2: $alter = &$this -> Timestamp; break;
				case 3: $alter = &$this -> Title; break;
				case 4: $alter = &$this -> Body; break;
			}
			
			if ($char === '<' && $this -> state > 0) {
				$new_state = 0;
				$alter = &$this -> Buffer;
			}
			
			$alter .= $char;
			
			switch ($this -> Buffer) {
				case '<nitem><nsource>': $new_state = 1; break;
				case '</nsource><ndate>': $new_state = 2; break;
				case '</ndate><ntitle>': $new_state = 3; break;
				case '</ntitle><nbody>': $new_state = 4; break;
			}
			
			if ($new_state > -1) {
				$this -> state = $new_state;
				if ($new_state > 0) $this -> Buffer = '';
			}
			
			if ($this -> Buffer === '</nbody></nitem>') {
				$this -> add();
			}
			end:
		}
	}

	class FeedReader {
		private $DB, $fType, $Config, $Times, $Count;
		private $FSM = array();
		private $Last_Time;
		
		function __construct($fType) {
			date_default_timezone_set('GMT');
			global $Config;
			
			$this -> DB = new Database();
			$this -> fType = $fType;
			$this -> Config = &$Config;
			$this -> Times = array(
				'open' => strtotime($Config -> Root['Feeds']['FtseOpen']),
				'close' => strtotime($Config -> Root['Feeds']['FtseClose'])
			);
			$this -> FSM['Ftse'] = new Ftse_FSM();
			$this -> FSM['News'] = new News_FSM();
			$this -> Last_Time = -1;
			$this -> readFeed();
		}
		
		private function parseData($data) {
			if ($this -> fType === 'News') {
				$rows = $this -> FSM['News'] -> read($data);
				$this -> DB -> insert($rows, 'Unprocessed_News', true);
			} elseif ($this -> fType === 'Ftse') {
				$t = time();
				if (
					$t < $this -> Times['open'] || $t > $this -> Times['close']
					|| in_array(date('D'), array('Sat', 'Sun'))
				) {
					return;
				}
				
				$rows = $this -> FSM['Ftse'] -> read($data);
				
				foreach ($rows as $k => $row) {
					if ($this -> Last_Time < 0) $this -> Last_Time = $row['Timestamp'];
					if ($this -> Last_Time == $row['Timestamp']) continue;
					if ($this -> Last_Time < $row['Timestamp'] && $this -> Last_Time + 50 > $row['Timestamp']) unset($rows[$k]);
					elseif ($this -> Last_Time < $row['Timestamp']) $this -> Last_Time = $row['Timestamp'];
				}
				
				if (count($rows) > 0) $this -> DB -> insert($rows, 'Unprocessed_Ftse');
			}
		}
		
		private function readFeed() {
			$h = stream_socket_client('tcp://' . FEED . ':' . $this -> Config -> Root['Feeds'][$this -> fType][1], $errno, $errstr, 30);
			echo 'Being fed...';
			
			while (!feof($h)) {				
				$chunks = fread($h, 8192);
				echo ' nom';
				$this -> parseData($chunks);
			}
			
			echo 'Finished eating.', "\n";
			fclose($h);
		}
	}
?>